#include "CollisionArbiter.h"
#include "Scene.h"
//#include "NotificationServer.h"
#include "Transformation.h"
#include "BoxCollider.h"
#include "Terrain.h"
#include "Point.h"
#include "Tile.h"
#include "PhysicsUtils.h"
#include "Logger.h"
#include "Exception.h"

using namespace game_engine;
using namespace game_engine::components;
using namespace game_engine::tile_engine;
using namespace game_engine::physics;
using namespace game_engine::utils;

///////////////////////////////////////////////////////////////////////////////
// Constructors and Destructors
///////////////////////////////////////////////////////////////////////////////

CollisionArbiter::CollisionArbiter(void)
{
}

CollisionArbiter::~CollisionArbiter(void)
{
}

///////////////////////////////////////////////////////////////////////////////
// Other public methods
///////////////////////////////////////////////////////////////////////////////

//void CollisionArbiter::initialize(SceneManager *manager)
//{
//    h2 = worldCollision.attach(manager, &SceneManager::worldCollision);
//}

//void CollisionArbiter::update(const Scene *scene) throw(...)
//{
//    NotificationServer *server = NotificationServer::getInstance();
//    vector<GameObject*> objects = server->takeMovedObjects();
//
//    if (!objects.empty())
//    {
//        // Evaluate collisions with world
//        evaluateWorldCollisions(scene, objects);
//
//        // Evaluate collisions between objects
//        evaluateInterCollisions(objects);
//    }
//}

//void CollisionArbiter::finalize(void)
//{
//    worldCollision.detach(h2);
//}

const vector<GameObject*> CollisionArbiter::evaluateWorldCollisions(const Scene *scene, 
    const vector<GameObject*> &objects) throw(...)
{
    vector<GameObject*> collidingObjects;

    // Obtain reference to World object, and to its Terrain component
    // TODO : change this --> use Tag instead of Name
    GameObject *world = scene->getObjectByName("World");
    Terrain *terrain = dynamic_cast<Terrain*> (world->getComponent("Terrain"));

    // Loop through all elements
    vector<GameObject*>::const_iterator it;
    for(it=objects.begin(); it<objects.end(); it++)
    {
        //Logger::DEBUG(GAMEENGINE_LOG, "The object " + (*it)->getName() + 
        //    " has moved");
        // Obtain reference to object's Transformation and BoxCollider components
        Transformation *objTransf;
        BoxCollider *objCollider;
        try
        {
            objTransf = dynamic_cast<Transformation*> 
                ((*it)->getComponent("Transformation"));
            objCollider = dynamic_cast<BoxCollider*> 
                ((*it)->getComponent("BoxCollider"));
        } catch (exception)
        {
            // This branch must never be covered ...
            continue;
        }
        
        // Get object's current position
        Point objPosition = objTransf->getPosition();

        // Get surrounding tiles and their corresponding box colliders
        vector<Tile*> tiles = terrain->getSurroundingTiles(objPosition);
        if (tiles.empty())
        {
            continue;
        }

        // Check whether the object collides with any surrounding tile
        Rect objBox = objCollider->getCollider(objPosition);
        vector<Tile*>::iterator i;
        for(i=tiles.begin(); i<tiles.end(); i++)
        {
            if (PhysicsUtils::collisionDetected(objBox, (*i)->getBoxCollider()))
            {
                // FIXME : In case tile type is not WALKABLE, a notification must
                // be sent to Scene Manager
                // If tile is not walkable
                if ((*i)->getTileType() != WALKABLE)
                {
                    //objTransf->revertPosition();
                    //worldCollision.notify(*it, (*i)->getTileType());
                    // TODO : send also information concerning tile type
                    collidingObjects.push_back(*it);
                    break;
                }
            }
        } // for(i=tiles.begin(); i<tiles.end(); i++)
    } // for(it=objects.begin(); it<objects.end(); it++)

    return collidingObjects;
}

const vector<pair<GameObject*, GameObject*>> CollisionArbiter::evaluateInterCollisions(
    const vector<GameObject*> &objects) throw(...)
{
    vector<pair<GameObject*, GameObject*>> collidingObjects;

    // Loop through all elements
    vector<GameObject*>::const_iterator i;    
    for(i=objects.begin(); i<(objects.end()-1); i++)
    {
        // Obtain reference to first object's BoxCollider component
        Transformation *obj1Transf = dynamic_cast<Transformation*> 
            ((*i)->getComponent("Transformation"));
        BoxCollider *obj1Collider = dynamic_cast<BoxCollider*> 
            ((*i)->getComponent("BoxCollider"));

        vector<GameObject*>::const_iterator j;
        for(j=(i+1); j<objects.end(); j++)
        {
            // Obtain reference to second object's BoxCollider component
            Transformation *obj2Transf = dynamic_cast<Transformation*> 
                ((*j)->getComponent("Transformation"));
            BoxCollider *obj2Collider = dynamic_cast<BoxCollider*> 
                ((*j)->getComponent("BoxCollider"));

            // Verify collision between them
            if (PhysicsUtils::collisionDetected(
                obj1Collider->getCollider(obj1Transf->getPosition()), 
                obj2Collider->getCollider(obj2Transf->getPosition())))
            {
                //Logger::INFO(GAMEENGINE_LOG, "Objects " + (*i)->getName() + 
                //    " and " + (*j)->getName() + " are colliding!!");
                pair <GameObject*, GameObject*> duple = make_pair((*i), (*j));
                collidingObjects.push_back(duple);
            }
        } // for(j=i; j<objects.end(); j++)
    } // for(i=objects.begin(); i<(objects.end()-1); i++)

    return collidingObjects;
}

///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////

//void CollisionArbiter::evaluateWorldCollisions(const Scene *scene, 
//                                               const vector<GameObject*> &objects) 
//    throw(...)
//{
//    // Obtain reference to World object, and to its Terrain component
//    // TODO : change this --> use Tag instead of Name
//    GameObject *world = scene->getObjectByName("World");
//    Terrain *terrain = dynamic_cast<Terrain*> (world->getComponent("Terrain"));
//
//    // Loop through all elements
//    vector<GameObject*>::const_iterator it;
//    for(it=objects.begin(); it<objects.end(); it++)
//    {
//        //Logger::DEBUG(GAMEENGINE_LOG, "The object " + (*it)->getName() + 
//        //    " has moved");
//        // Obtain reference to object's Transformation and BoxCollider components
//        Transformation *objTransf;
//        BoxCollider *objCollider;
//        try
//        {
//            objTransf = dynamic_cast<Transformation*> 
//                ((*it)->getComponent("Transformation"));
//            objCollider = dynamic_cast<BoxCollider*> 
//                ((*it)->getComponent("BoxCollider"));
//        } catch (exception)
//        {
//            // This branch must never be covered ...
//            continue;
//        }
//        
//        // Get object's current position
//        Point objPosition = objTransf->getPosition();
//
//        // Get surrounding tiles and their corresponding box colliders
//        vector<Tile*> tiles = terrain->getSurroundingTiles(objPosition);
//        if (tiles.empty())
//        {
//            continue;
//        }
//
//        // Check whether the object collides with any surrounding tile
//        Rect objBox = objCollider->getCollider(objPosition);
//        vector<Tile*>::iterator i;
//        for(i=tiles.begin(); i<tiles.end(); i++)
//        {
//            if (PhysicsUtils::collisionDectected(objBox, (*i)->getBoxCollider()))
//            {
//                // FIXME : In case tile type is not WALKABLE, a notification must
//                // be sent to Scene Manager
//                // If tile is not walkable
//                if ((*i)->getTileType() != WALKABLE)
//                {
//                    //objTransf->revertPosition();
//                    //worldCollision.notify(*it, (*i)->getTileType());
//                    break;
//                }
//            }
//        } // for(i=tiles.begin(); i<tiles.end(); i++)
//    } // for(it=objects.begin(); it<objects.end(); it++)
//}

//void CollisionArbiter::evaluateInterCollisions(const vector<GameObject*> &objects) 
//    throw(...)
//{
//    // Loop through all elements
//    vector<GameObject*>::const_iterator i;    
//    for(i=objects.begin(); i<(objects.end()-1); i++)
//    {
//        // Obtain reference to first object's BoxCollider component
//        Transformation *obj1Transf = dynamic_cast<Transformation*> 
//            ((*i)->getComponent("Transformation"));
//        BoxCollider *obj1Collider = dynamic_cast<BoxCollider*> 
//            ((*i)->getComponent("BoxCollider"));
//
//        vector<GameObject*>::const_iterator j;
//        for(j=(i+1); j<objects.end(); j++)
//        {
//            // Obtain reference to second object's BoxCollider component
//            Transformation *obj2Transf = dynamic_cast<Transformation*> 
//                ((*j)->getComponent("Transformation"));
//            BoxCollider *obj2Collider = dynamic_cast<BoxCollider*> 
//                ((*j)->getComponent("BoxCollider"));
//
//            // Verify collision between them
//            if (PhysicsUtils::collisionDectected(obj1Collider->getCollider(obj1Transf->getPosition()), 
//                obj2Collider->getCollider(obj2Transf->getPosition())))
//            {
//                Logger::INFO(GAMEENGINE_LOG, "Objects " + (*i)->getName() + 
//                    " and " + (*j)->getName() + " are colliding!!");
//            }
//
//        } // for(j=i; j<objects.end(); j++)
//    } // for(i=objects.begin(); i<(objects.end()-1); i++)
//
//}
